const Note = require('lib/models/Note.js');
const Alarm = require('lib/models/Alarm.js');

class AlarmService {

	static setDriver(v) {
		this.driver_ = v;
	}

	static driver() {
		if (!this.driver_) throw new Error('AlarmService driver not set!');
		return this.driver_;
	}

	static setLogger(v) {
		this.logger_ = v;
	}

	static logger() {
		return this.logger_;
	}

	static setInAppNotificationHandler(v) {
		this.inAppNotificationHandler_ = v;
		if (this.driver_.setInAppNotificationHandler) this.driver_.setInAppNotificationHandler(v);
	}

	static async garbageCollect() {
		this.logger().info('Garbage collecting alarms...');

		// Delete alarms that have already been triggered
		await Alarm.deleteExpiredAlarms();

		// Delete alarms that correspond to non-existent notes
		const alarmIds = await Alarm.alarmIdsWithoutNotes();
		for (let i = 0; i < alarmIds.length; i++) {
			this.logger().info('Clearing notification for non-existing note. Alarm ' + alarmIds[i]);
			await this.driver().clearNotification(alarmIds[i]);
		}
		await Alarm.batchDelete(alarmIds);
	}

	// When passing a note, make sure it has all the required properties 
	// (better to pass a complete note or else just the ID)
	static async updateNoteNotification(noteOrId, isDeleted = false) {
		let note = null;
		let noteId = null;

		if (typeof noteOrId === 'object') {
			note = noteOrId;
			noteId = note.id;
		} else {
			note = await Note.load(noteOrId);
			noteId = note ? note.id : null;
		}

		if (!note && !isDeleted) return;

		const driver = this.driver();

		let alarm = noteId ? await Alarm.byNoteId(noteId) : null;
		let clearAlarm = false;

		if (isDeleted ||
		    !Note.needAlarm(note) ||
		    (alarm && alarm.trigger_time !== note.todo_due))
		{
			clearAlarm = !!alarm;
		}

		if (!clearAlarm && alarm) { // Alarm already exists and set at the right time

			// For persistent notifications (those that stay active after the app has been closed, like on mobile), if we have
			// an alarm object we can be sure that the notification has already been set, so there's nothing to do.
			// For non-persistent notifications however we need to check that the notification has been set because, for example,
			// if the app has just started the notifications need to be set again. so we do this below.
			if (!driver.hasPersistentNotifications() && !driver.notificationIsSet(alarm.id)) {
				const notification = await Alarm.makeNotification(alarm, note);
				this.logger().info('Scheduling (non-persistent) notification for note ' + note.id, notification);
				driver.scheduleNotification(notification);
			}

			return;
		}

		if (clearAlarm) {
			this.logger().info('Clearing notification for note ' + noteId);
			await driver.clearNotification(alarm.id);
			await Alarm.delete(alarm.id);
		}

		if (isDeleted || !Note.needAlarm(note)) return;

		await Alarm.save({
			note_id: note.id,
			trigger_time: note.todo_due,
		});

		// Reload alarm to get its ID
		alarm = await Alarm.byNoteId(note.id);

		const notification = await Alarm.makeNotification(alarm, note);
		this.logger().info('Scheduling notification for note ' + note.id, notification);
		await driver.scheduleNotification(notification);
	}

	static async updateAllNotifications() {
		this.logger().info('Updating all notifications...');

		await this.garbageCollect();

		const dueNotes = await Note.dueNotes();
		for (let i = 0; i < dueNotes.length; i++) {
			await this.updateNoteNotification(dueNotes[i]);
		}
	}

}

module.exports = AlarmService;